/**
 * 剑指 Offer 20. 表示数值的字符串
 * https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
 */

const code0 = '0'.charCodeAt(0);
const code9 = '9'.charCodeAt(0);
const codePlus = '+'.charCodeAt(0);
const codeMinus = '-'.charCodeAt(0);
const codePoint = '.'.charCodeAt(0);
const codee = 'e'.charCodeAt(0);
const codeE = 'E'.charCodeAt(0);
const codeSpace = ' '.charCodeAt(0);

/**
 * @param {string} s
 * @return {boolean}
 */
function isNumber(s) {
  /**
   * 0 - 刚开始匹配，下一个是符号、数字或小数点
   * 1 - 匹配到了符号，下一个是数字或小数点
   * 2 - 匹配到了整数，下一个是数字、小数点或 E 或结尾
   * 3 - 匹配到了小数点，下一个是数字或 E 或结尾
   * 4 - 匹配到了小数，下一个是数字或 E 或结尾
   * 5 - 匹配到了 E，下一个是符号或数字
   * 6 - 匹配到了指数符号，下一个是数字
   * 7 - 匹配到了指数整数，下一个是数字或结尾
   * @type {number}
   */
  let step = 0;
  let begin = 0;
  let end = s.length - 1;
  for (; begin <= end; begin += 1) {
    if (s.charCodeAt(begin) !== codeSpace) {
      break;
    }
  }
  for (; end >= begin; end -= 1) {
    if (s.charCodeAt(end) !== codeSpace) {
      break;
    }
  }
  if (begin > end) {
    return false;
  }
  let leftNumberComplete = false;
  for (let i = begin; i <= end; i += 1) {
    const code = s.charCodeAt(i);
    switch (step) {
      case 0:
        if (code === codePlus || code === codeMinus) {
          step = 1;
        } else if (code >= code0 && code <= code9) {
          step = 2;
          leftNumberComplete = true;
        } else if (code === codePoint) {
          step = 3;
        } else {
          return false;
        }
        break;
      case 1:
        if (code >= code0 && code <= code9) {
          step = 2;
          leftNumberComplete = true;
        } else if (code === codePoint) {
          step = 3;
        } else {
          return false;
        }
        break;
      case 2:
        if (code >= code0 && code <= code9) {
          //
        } else if (code === codePoint) {
          step = 3;
        } else if (code === codeE || code === codee) {
          step = 5;
        } else {
          return false;
        }
        break;
      case 3:
        if (code >= code0 && code <= code9) {
          step = 4;
          leftNumberComplete = true;
        } else if (code === codeE || code === codee) {
          step = 5;
        } else {
          return false;
        }
        break;
      case 4:
        if (code >= code0 && code <= code9) {
          //
        } else if (code === codeE || code === codee) {
          step = 5;
        } else {
          return false;
        }
        break;
      case 5:
        if (!leftNumberComplete) {
          return false;
        }
        if (code === codePlus || code === codeMinus) {
          step = 6;
        } else if (code >= code0 && code <= code9) {
          step = 7;
        } else {
          return false;
        }
        break;
      case 6:
        if (code >= code0 && code <= code9) {
          step = 7;
        } else {
          return false;
        }
        break;
      case 7:
        if (code >= code0 && code <= code9) {
          //
        } else {
          return false;
        }
        break;
      default:
        break;
    }
  }
  switch (step) {
    case 2: case 4: case 7:
      return true;
    case 3:
      return leftNumberComplete;
    default:
      return false;
  }
}

['+100', '5e2', '-123', '3.1416', '-1E-16', '0123', '0', '    .1  ', '3.'].forEach((s) => {
  console.log(isNumber(s) === true);
});
['12e', '1a3.14', '1.2.3', '+-5', '12e+5.4', 'e', '.', '.e1'].forEach((s) => {
  console.log(isNumber(s) === false);
});
